import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.Semaphore;

public class Client implements Runnable 
{
	Socket socket;
	Player connectedPlayer = null;
	PrintWriter writer;
	BufferedReader reader;
	Thread clientThread = null;
	
	Semaphore clientSemaphore = new Semaphore(1);
	Semaphore sendingSemaphore = new Semaphore(1);
	static Semaphore visibilityCheckingSemaphore = new Semaphore(1);
	private Object o = new Object();
	
	public Client(Socket socket_) throws InterruptedException
	{
		socket = socket_;
		try 
		{
			writer = new PrintWriter(socket.getOutputStream());
			writer.flush();
			reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		} 
		catch (IOException e1)
		{
			e1.printStackTrace();
		}
		clientThread = new Thread(this);
		System.err.println("client const");
		clientSemaphore.acquire();
		clientThread.start();
	}
	
	void attackHandler(String[] message) throws InterruptedException
	{
		String gesture = message[0];
		String targetName = message[1];
		System.err.println("A_attack_hdl");
		Clients.getInstance().allClientsSemaphore.acquire();
		Client target = Clients.getInstance().returnClient(targetName);
		Gesture foundGesture = connectedPlayer.gestureIsEnabled(gesture);
		if( foundGesture!= null)
		{
			System.err.println("Wykonywanie gestu " + foundGesture.stringDescription );
			System.err.println("na graczu " + target.connectedPlayer.name);
			boolean targetWereKilled = foundGesture.action(this, target);
			for (Client client : Clients.getInstance().clients) 
			{
				if(targetWereKilled)
				{
					if(client == target)
					{
						client.sendMessage(MessageFactory.makeStatisticsMessage(client, true));
						client.connectedPlayer.nearlyPlayers.remove(target.connectedPlayer);
					}
					else
					{
						client.connectedPlayer.nearlyPlayers.remove(target.connectedPlayer);
						boolean self = (client == this);
						if(client.checkVisibility(this))
						{
							client.sendMessage(MessageFactory.makeStatisticsMessage(this, self));
						}
						if(client.checkVisibility(target))
						{
								client.sendMessage(MessageFactory.makeDeletePlayerMessage(target));
						}
					}
				}
				else
				{
					boolean self = (client == this);
					if(client.checkVisibility(this))
					{
						client.sendMessage(MessageFactory.makeStatisticsMessage(this, self));
					}
					if(target!= this)
					{
						self = (client == target);
						if(client.checkVisibility(target))
						{
							client.sendMessage(MessageFactory.makeStatisticsMessage(target, self));
						}
					}
				}
			}
			Clients.getInstance().allClientsSemaphore.release();
			if(targetWereKilled)
			{
				try
				{
					System.err.println("A_target killed");
					target.clientSemaphore.acquire();
					target.clientThread.interrupt();
					Players.getInstance().playersSemahore.acquire();
					Players.getInstance().players.remove(target.connectedPlayer);
					Players.getInstance().playersSemahore.release();
					if(target.clientThread.isInterrupted())
					{
						System.err.println("zatrzymano watek");
					}
					else
					{
						System.err.println("watek dziala dalej");						
					}
				}
				catch (NullPointerException e) 
				{
					// TODO: handle exception
					//System.err.println("null reference+++++++++++++++++++++++++++++");
				}
				finally
				{
					target.clientSemaphore.release();
				}
				target.deleteClient();
			}
		}
		else
		{
			Clients.getInstance().allClientsSemaphore.release();
			System.err.println("gest niedozwolony w tej chwili");
		}
	}
	
	void loginHandler(String login) throws InterruptedException
	{
		if(login == null || login == "")
		{
			return;
		}
		System.err.println("LOGOWANIE");
		StringBuilder build = new StringBuilder();
		Player player = new Player(login);
		//Players.getInstance().playersSemahore.acquire();
		System.err.println("A_login_hdl");
		Clients.getInstance().allClientsSemaphore.acquire();
		if(Players.getInstance().players.contains(player))
		{
			System.err.println("Jest player");
			
			for (Client client : Clients.getInstance().clients) 
			{
				if(player.equals(client.connectedPlayer))
				{
					//Players.getInstance().playersSemahore.acquire();
					System.err.println("player podlaczony");
					build.append(MessageType.REGISTRY_ERROR);
					sendMessage(build.toString());
					return;
				}
			}

		}
		else
		{
			Players.getInstance().players.add(player);
		}
		Clients.getInstance().allClientsSemaphore.release();
		System.err.println("A_login_hdl_client");
		clientSemaphore.acquire();
		//Players.getInstance().playersSemahore.release();
		connectedPlayer = player;
		build.append(MessageType.OK);
		clientSemaphore.release();
		sendMessage(build.toString());
		sendMessage(MessageFactory.makeStatisticsMessage(this, true));
		
		//Clients.getInstance().debug();
	}
	
	void positionHandler(String[] message) throws InterruptedException
	{
		System.err.println("POZYCJONOWANIE");
		try
		{
			String messageToClient = null;
			System.err.println("A_pos_hdl_client");
			clientSemaphore.acquire();
			connectedPlayer.position.xPosition = Integer.parseInt(message[0]);
			connectedPlayer.position.yPosition = Integer.parseInt(message[1]);
			clientSemaphore.release();
			System.err.println("A_pos_hdl_ALL");
			Clients.getInstance().allClientsSemaphore.acquire();
			for (Client client : Clients.getInstance().getClients()) 
			{
				messageToClient = null;
				if(this == client)
				{
					continue;
				}
				if(client.connectedPlayer == null)
				{
					continue;
				}
				/*
				 * checking that we can see other player (with this client sight)
				 */
				if(checkVisibility(client))
				{
					if(!connectedPlayer.nearlyPlayers.contains(client.connectedPlayer))
					{
						connectedPlayer.nearlyPlayers.add(client.connectedPlayer);
						messageToClient = MessageFactory.makeAddPlayerMessage(client);
					}
					else
					{
						messageToClient = MessageFactory.makePositionMessage(client);
					}
				}
				else
				{
					if(connectedPlayer.nearlyPlayers.contains(client.connectedPlayer))
					{
						messageToClient = MessageFactory.makeDeletePlayerMessage(client);
						connectedPlayer.nearlyPlayers.remove(client.connectedPlayer);
					}
				}
				if(messageToClient != null)
				{
					sendMessage(messageToClient);
				}
				if(client.checkVisibility(this))
				{
					messageToClient = null;
					if(!client.connectedPlayer.nearlyPlayers.contains(connectedPlayer))
					{
						client.connectedPlayer.nearlyPlayers.add(connectedPlayer);
						messageToClient = MessageFactory.makeAddPlayerMessage(this);
					}
					else
					{
						messageToClient = MessageFactory.makePositionMessage(this);
					}
				}
				else
				{
					if(client.connectedPlayer.nearlyPlayers.contains(connectedPlayer))
					{
						messageToClient = MessageFactory.makeDeletePlayerMessage(this);
						client.connectedPlayer.nearlyPlayers.remove(client.connectedPlayer);
					}
				}
				if(messageToClient != null)
				{
					client.sendMessage(messageToClient);
				}
			}
			Clients.getInstance().allClientsSemaphore.release();
		}
		catch(NumberFormatException e)
		{
			//just break, bad format of incoming data
		}
	}
	
	boolean checkVisibility(Client client) throws InterruptedException
	{
		System.err.println("visibility_hdl");
		visibilityCheckingSemaphore.acquire();
		System.err.println("visibility_hdl_this");
		clientSemaphore.acquire();
		if(connectedPlayer == null)
		{
			clientSemaphore.release();
			visibilityCheckingSemaphore.release();
			return false;
		}
		Attribute sight = connectedPlayer.getAttribute("Sight");
		if(sight == null)
		{
			clientSemaphore.release();
			visibilityCheckingSemaphore.release();
			return true;
		}
		if(this == client)
		{
			clientSemaphore.release();
			visibilityCheckingSemaphore.release();
			return true;
		}
		System.err.println("visibility_hdl_client");
		client.clientSemaphore.acquire();
		Attribute invisible = client.connectedPlayer.getAttribute("Invisible");
		if(invisible != null)
		{
			if(invisible.value == Attribute.ON)
			{
				client.clientSemaphore.release();
				clientSemaphore.release();
				visibilityCheckingSemaphore.release();
				return false;
			}
		}
		double dx = connectedPlayer.position.xPosition - client.connectedPlayer.position.xPosition;
		double dy = connectedPlayer.position.yPosition - client.connectedPlayer.position.yPosition;
		boolean ret =  ((double)sight.value * (double)sight.value) >= dx * dx + dy * dy;
		client.clientSemaphore.release();
		clientSemaphore.release();
		visibilityCheckingSemaphore.release();
		return ret;
	}
	
	public void run()
	{
		try
		{
			clientSemaphore.release();
			String message;
			System.out.println("Connection received from " + socket.getInetAddress().getHostName());
			while(true)
			{
				if(Thread.interrupted())
				{
					deleteClient();
					System.err.println("Wychodze z watku");
					return;
				}
				message = reader.readLine();
				System.err.println("odebrano: "+ (message == null ? "NULL" : message));
				if(message == null)
				{
					System.err.println("Otrzymalem null package - zamykam socket");
					System.err.println("run_null_msg_client");
					clientSemaphore.acquire();
					connectedPlayer = null;
					clientSemaphore.release();
					deleteClient();
					break;
				}
				else
				{
					messageHandler(message);
				}
			}
		}
		catch(IOException | InterruptedException ioException)
		{
		}
		finally
		{
			try
			{
				if(Thread.interrupted())
				{
					deleteClient();
					System.err.println("Wychodze z watku " + clientThread.getId());
					Clients.getInstance().debug();
					return;
				}
				deleteClient();
			}
			catch(InterruptedException e)
			{
			}
		}
	}
	
	void deleteClient() throws InterruptedException
	{
		System.err.println("delete_client");
		clientSemaphore.acquire();
		connectedPlayer = null;
		clientSemaphore.release();
		try
		{
			System.err.println("writer closed");
			System.err.println("delete_client_sending");
			sendingSemaphore.acquire();
			socket.close();
			sendingSemaphore.release();
			System.err.println("socket zamkniety " + socket.isClosed());
		}
		catch(IOException ioException)
		{
			ioException.printStackTrace();
		}
		finally
		{
			System.err.println("delete_ALL");
			Clients.getInstance().allClientsSemaphore.acquire();
			int index = Clients.getInstance().clients.indexOf(this);
			if(index != -1)
			{
				Clients.getInstance().clients.set(index, null);
				Clients.getInstance().clients.remove(index);
			}
			Clients.getInstance().allClientsSemaphore.release();
		}
	}
		
	void sendMessage(String msg)
	{
		try
		{
			System.err.println("send_client");
			clientSemaphore.acquire();
			System.out.println("server to >"  + (connectedPlayer == null ? "NEW CLIENT" : connectedPlayer.name )+ " " + msg);
			clientSemaphore.release();
			System.err.println("send_sending");
			sendingSemaphore.acquire();
			writer.println(msg);
			writer.flush();
		}
		catch(InterruptedException ioException){
			ioException.printStackTrace();
		}
		finally
		{
			sendingSemaphore.release();
		}
	}
		
	void messageHandler(String buffer) throws InterruptedException
	{
		String[] message = buffer.substring(1).split(MessageType.DELIMITER);
		System.err.println("msg_hdl_client");
		clientSemaphore.acquire();
		if(connectedPlayer == null)
		{
			clientSemaphore.release();
			if(parseType(buffer) == MessageType.LOGIN)
			{
				loginHandler(message[0]);
				return;
			}
			else
			{
				System.err.println("Nie zalogowano");
				System.err.println("otrzymano COS przed zalogowaniem:\n" + buffer);
				return;
			}
		}
		clientSemaphore.release();
		switch(parseType(buffer))
		{
			case MessageType.POSITION:
			{
				positionHandler(message);
				break;
			}
			case MessageType.ATTACK:
			{
				attackHandler(message);
				break;
			}
			case MessageType.UNKNOWN:
			default:
			{
				System.err.println("otrzymano COS:\n" + buffer);
				break;
			}
		}
	}
	
	int parseType(String buffer)
	{
		if(buffer.charAt(0)== MessageType.LOGIN) 
		{
			return MessageType.LOGIN;
		}
		if(buffer.charAt(0) == MessageType.POSITION)
		{
			return MessageType.POSITION;
		}
		if(buffer.charAt(0) == MessageType.ATTACK)
		{
			return MessageType.ATTACK;
		}
		return MessageType.UNKNOWN;	
	}
}